// luanetdll512.h

#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
#include "lstate.h"
#include <stdlib.h>

#pragma once

LUALIB_API int luaopen_luanet(lua_State *L);
static int luanet_proxycall(lua_State *L);

using namespace System;
using namespace System::Runtime::InteropServices;

namespace SmartMathLibrary {
	namespace Lua {
	public ref class lua51	
	{
		public:
			// delegates for calling custom functions from Lua
			delegate void outputfx(String ^Txt);
			delegate int functioncall();
			outputfx ^gofx;
			lua51()
			{
				state = lua_open();
				luaL_openlibs(state);
				luaopen_luanet(state);

				ProxyList = gcnew array<functioncall^>(10);
				ProxySize = 0;
			}
			~lua51()
			{
				lua_close(state);
			}
			void ConnectDotNet(outputfx ^ofx,lua51 ^owner)
			{
				GCHandle gch = GCHandle::Alloc(owner);
				state->l_G->luanet = (void*)GCHandle::ToIntPtr(gch);
				gofx = ofx;
			}
			void RegisterFunction(String ^Name,functioncall ^func)
			{
				char *Name2 = NETtoC_str(Name);
				ProxySize++;
				if (ProxySize > ProxyList->Length)
					ProxyList->Resize(ProxyList,ProxySize + 5);
				ProxyList[ProxySize - 1] = func;
				lua_pushinteger(state,ProxySize - 1);
				lua_pushcclosure(state,luanet_proxycall,1);
				lua_setglobal(state,Name2);
				free(Name2);
			}
			Object^ GetParameter(int Number)
			{
				return GetFromStack(Number);
			}
			double GetParameterNumber(int Number)
			{
				return luaL_checknumber(state,Number);
			}
			Object^ GetGlobal(String ^Variable)
			{
				char *Code2 = NETtoC_str(Variable);
	 			lua_getglobal(state,Code2);
				return GetFromStack(-1);
			}
			void ReturnString(String ^Str)
			{
				char *cstr = NETtoC_str(Str);
				lua_pushstring(state,cstr);
				free(cstr);
			}
			void ReturnInt(int number)
			{
				lua_pushnumber(state,number);
			}
			void ReturnDouble(double number)
			{
				lua_pushnumber(state,number);
			}
            int Compile(String ^Code)
		    {
				int  Result;
				char *Code2 = NETtoC_str(Code);
				Result = luaL_loadstring(state,Code2);
				free(Code2);				
				if (Result != 0)
					gofx(gcnew String(lua_tostring(state,-1)));
				return Result;
			}
			int DoString(String ^Code)
			{
				int  Result;
				char *Code2 = NETtoC_str(Code);
				Result = luaL_dostring(state,Code2);
				free(Code2);				
				if (Result != 0)
					gofx(gcnew String(lua_tostring(state,-1)));
				return Result;
			}
			String^ GetParameterString(int Number)
			{
				const char *str = luaL_checkstring(state,Number);
                return gcnew String(str);
			}
			String^ GetLastError()
			{
				if (!lua_isnil(state,-1))
				{
					String ^Message;
					const char *msg = lua_tostring(state, -1);
					if (msg == NULL) 
						msg = "(error object is not a text message)";
					Message = gcnew String(msg);
					lua_pop(state, 1);
					return Message;
				}
				else
					return String::Empty;
			}
			array<functioncall^> ^ProxyList;
			int ProxySize;
		private:
			lua_State *state;

			Object^ GetFromStack(int Number)
			{
				switch(lua_type(state,Number))
				{
					case LUA_TNONE:
					case LUA_TNIL:
					case LUA_TBOOLEAN:
					case LUA_TLIGHTUSERDATA:
					case LUA_TTABLE:
					case LUA_TFUNCTION:
					case LUA_TUSERDATA:
					case LUA_TTHREAD:
						return nullptr;
						break;
					case LUA_TSTRING:
						{
							String ^Str = gcnew String(lua_tostring(state,Number));
							return Str;
						}
						break;
					case LUA_TNUMBER:
						double d = lua_tonumber(state,Number);
						return d;
				}
				return nullptr;

			}

			char * NETtoC_str(System::String ^src)
			{
				char *dst = (char*)malloc(src->Length + 1);
				int len = src->Length;
  				for (int i = 0; i < len; ++i) 
				{
					 wchar_t wc = src->ToCharArray(i,1)[0];
					 if (wc < 0 || wc > 255)
						 return 0; // couldn't translate.
					 dst[i] = char(wc);
				 }
				 dst[len] = '\0';
				 return dst;
			}			
	};
}
}